Simple vs Oneshot - Choisir un type de service systemd

https://trstringer.com/simple-vs-oneshot-systemd-service/

 

Lorsque vous créez votre service systemd, le choix du type de service peut être un peu délicat et déroutant. Il y a une poignée de types de services disponibles, mais ce post se concentrera sur les différences entre les services oneshot et simples. Il peut y avoir une certaine confusion quant à savoir lequel utiliser, et quand l'utiliser.

S'il est réglé sur simple (la valeur par défaut si ExecStart= est spécifié mais que ni Type= ni BusName= ne le sont), le gestionnaire de service considérera que l'unité a démarré immédiatement après que le processus de service principal ait été bifurqué. Il est attendu que le processus configuré avec ExecStart= soit le processus principal du service. Dans ce mode, si le processus offre des fonctionnalités à d'autres processus du système, ses canaux de communication doivent être installés avant le démarrage du service (par exemple, les sockets configurés par systemd, via l'activation des sockets), car le gestionnaire de services procédera immédiatement au démarrage des unités de suivi, juste après la création du processus principal du service, et avant l'exécution du binaire du service. Notez que cela signifie que les lignes de commande systemctl start pour les services simples signaleront un succès même si le binaire du service ne peut pas être invoqué avec succès (par exemple parce que l'utilisateur sélectionné n'existe pas, ou que le binaire du service est manquant).

Le comportement de oneshot est similaire à celui de simple ; cependant, le gestionnaire de services considérera que l'unité a démarré après la sortie du processus principal. Il lancera ensuite les unités suivantes. RemainAfterExit= est particulièrement utile pour ce type de service. Type=oneshot est le défaut implicite si ni Type= ni ExecStart= ne sont spécifiés.

J'ai trouvé que c'était un bon point de départ pour comprendre, mais cela peut toujours vous laisser vous demander quoi choisir et quand, surtout si vous n'avez pas d'unités de suivi.

Calendrier des unités de suivi

La plus grande différence entre les services simple et les services oneshot est le moment où les unités de suivi vont démarrer. Comme mentionné dans les pages de manuel, les unités de suivi d'un service simple démarrent immédiatement. Voici une illustration pour le montrer :

Service simple et timing des unités de suivi

Simple service diagramSimple service diagram
 

En revanche, dans le cas d'un service "one shot", toutes les unités de suivi attendent la fin du service avant de démarrer :

Service en un coup et calendrier des unités de suivi

Oneshot service diagramOneshot service diagram
 

Il y a cependant une raison importante derrière cet aspect qui entraîne d'autres différences de comportement. Tout ceci est dû aux différences d'états d'activation des services (plus d'informations sur les états d'activation ci-dessous).

Voici un exemple rapide qui le montre. Tout d'abord, voyons un service simple et une unité de suivi :

simple-test.service

[Unit]

Description=Simple service test

 

[Service]

Type=simple

ExecStart=/bin/bash -c "echo Simple service - start && sleep 60 && echo Simple service - end"

Et le service dépendant :

dep-simple-test.service

[Unit]

Description=Dependent service

After=simple-test.service

Requires=simple-test.service

 

[Service]

ExecStart=/bin/bash -c "echo Dependent service - running"

En démarrant dep-simple-test.service, simple-test.service démarrera en premier (à cause des directives After/Requires), et la journalisation s'affiche :

Jun 19 20:28:16 thstring20200619162314 systemd[1]: Started Simple service test.

Jun 19 20:28:16 thstring20200619162314 systemd[1]: Started Dependent service.

Jun 19 20:28:16 thstring20200619162314 bash[1238]: Simple service - start

Jun 19 20:28:16 thstring20200619162314 bash[1239]: Dependent service - running

Jun 19 20:28:16 thstring20200619162314 systemd[1]: dep-simple-test.service: Succeeded.

Jun 19 20:29:16 thstring20200619162314 bash[1238]: Simple service - end

Jun 19 20:29:16 thstring20200619162314 systemd[1]: simple-test.service: Succeeded.

Le test simple (et beaucoup d'autres ci-dessous) utilise simplement un sleep pour avoir une longue pause afin d'amplifier les différences de timing. Parce que simple-test.service est un service simple, son unité de suivi dep-simple-test.service démarrera immédiatement, et cela peut être vu par les deux services ayant été démarrés à peu près au même moment.

Mais si nous faisons la même chose avec un service oneshot, voyons à quoi cela ressemble.

oneshot-test.service

Unit]

Description=Oneshot service test

 

[Service]

Type=oneshot

ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

dep-oneshot-test.service

[Unit]

Description=Dependent service

After=oneshot-test.service

Requires=oneshot-test.service

 

[Service]

ExecStart=/bin/bash -c "echo Dependent service - running"

La journalisation pour ces deux unités (après avoir démarré dep-oneshot-test.service) montre la différence :

Jun 19 20:31:46 thstring20200619162314 systemd[1]: Starting Oneshot service test...

Jun 19 20:31:46 thstring20200619162314 bash[1420]: Oneshot service - start

Jun 19 20:32:46 thstring20200619162314 bash[1420]: Oneshot service - end

Jun 19 20:32:46 thstring20200619162314 systemd[1]: oneshot-test.service: Succeeded.

Jun 19 20:32:46 thstring20200619162314 systemd[1]: Started Oneshot service test.

Jun 19 20:32:46 thstring20200619162314 systemd[1]: Started Dependent service.

Jun 19 20:32:46 thstring20200619162314 bash[1440]: Dependent service - running

Jun 19 20:32:46 thstring20200619162314 systemd[1]: dep-oneshot-test.service: Succeeded.

Vous pouvez voir que le service Dependent ne démarre pas tant que le service Oneshot n'est pas terminé.

États d'activation

Les états d'activation des différents types de services contrôlent une grande partie de l'interaction avec les autres unités et contribuent de manière importante à la synchronisation.

Type

Before

During

After

Simple

inactive (dead)

active (running)

inactive (dead)

Oneshot

inactive (dead)

activating (start)

inactive (dead)

Oneshot (RemainAfterExit)

inactive (dead)

activating (start)

active (exited)

Beaucoup plus tard pour le oneshot avec RemainAfterExit. La différence d'état d'activation "pendant" entre le simple et le oneshot est la raison pour laquelle les unités de suivi attendent la fin d'un service oneshot, et pourquoi elles n'attendent pas la fin d'un service simple. C'est parce que les unités de suivi ne commencent pas avec un état d'activation.

RemainAfterExit (oneshot)

Après l'avoir évoqué plus haut, la directive RemainAfterExit modifie considérablement le comportement d'un service oneshot. C'est une façon de dire à systemd qu'après avoir quitté le service, il doit rester dans un état actif. Pour développer davantage avec un exemple :

oneshot-remainafterexit.service

[Unit]

Description=Oneshot service test with RemainAfterExit

 

[Service]

Type=oneshot

RemainAfterExit=yes

ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

En lançant systemctl status sur ce service après son exécution, nous pouvons voir la différence :

● oneshot-remainafterexit.service - Oneshot service test with RemainAfterExit

   Loaded: loaded (/etc/systemd/system/oneshot-remainafterexit.service; static; vendor preset: enabled)

   Active: active (exited) since Fri 2020-06-19 20:55:14 UTC; 7s ago

  Process: 1174 ExecStart=/bin/bash -c echo Oneshot service - start && sleep 60 && echo Oneshot service - end (code=exited, status=0/SUCCESS)

 Main PID: 1174 (code=exited, status=0/SUCCESS)

 

Jun 19 20:54:14 thstring20200619162314 systemd[1]: Starting Oneshot service test with RemainAfterExit...

Jun 19 20:54:14 thstring20200619162314 bash[1174]: Oneshot service - start

Jun 19 20:55:14 thstring20200619162314 bash[1174]: Oneshot service - end

Jun 19 20:55:14 thstring20200619162314 systemd[1]: Started Oneshot service test with RemainAfterExit.

Remarquez que le service est dans un état actif (quitté), au lieu d'être inactif (mort) (ce qui serait le cas si RemainAfterExit était désactivé). Mais quand voudrions-nous garder cela, et qu'est-ce que cela fait effectivement ? Voyons cela à l'aide d'un exemple qui utilise une directive ExecStop. ExecStop s'exécute lorsqu'un service est arrêté.

oneshot-execstop.service

[Unit]

Description=Oneshot service test with ExecStop

 

[Service]

Type=oneshot

RemainAfterExit=no

ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

ExecStop=/bin/bash -c "echo Oneshot service - stop"

Dans ce service, RemainAfterExit est désactivé (c'est la valeur par défaut, mais ajoutée pour être explicite).

● oneshot-execstop.service - Oneshot service test with ExecStop

   Loaded: loaded (/etc/systemd/system/oneshot-execstop.service; static; vendor preset: enabled)

   Active: inactive (dead)

 

Jun 19 21:04:10 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop...

Jun 19 21:04:10 thstring20200619162314 bash[1480]: Oneshot service - start

Jun 19 21:05:10 thstring20200619162314 bash[1480]: Oneshot service - end

Jun 19 21:05:10 thstring20200619162314 bash[1604]: Oneshot service - stop

Jun 19 21:05:10 thstring20200619162314 systemd[1]: oneshot-execstop.service: Succeeded.

Jun 19 21:05:10 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop.

Nous pouvons voir que l'ExecStop s'est exécuté immédiatement après l'ExecStart, car le service est passé à l'état inactif (mort). Voyons maintenant ce qui se passe avec le paramètre RemainAfterExit :

oneshot-execstop-remainafterexit.service

[Unit]

Description=Oneshot service test with ExecStop and RemainAfterExit

[Service]

Type=oneshot

RemainAfterExit=yes

ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

ExecStop=/bin/bash -c "echo Oneshot service - stop"

Et la sortie du statut systemctl :

● oneshot-execstop-remainafterexit.service - Oneshot service test with ExecStop and RemainAfterExit

   Loaded: loaded (/etc/systemd/system/oneshot-execstop-remainafterexit.service; static; vendor preset: enabled)

   Active: active (exited) since Fri 2020-06-19 21:07:54 UTC; 8s ago

  Process: 1708 ExecStart=/bin/bash -c echo Oneshot service - start && sleep 60 && echo Oneshot service - end (code=exited, status=0/SUCCESS)

 Main PID: 1708 (code=exited, status=0/SUCCESS)

 

Jun 19 21:06:54 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...

Jun 19 21:06:54 thstring20200619162314 bash[1708]: Oneshot service - start

Jun 19 21:07:54 thstring20200619162314 bash[1708]: Oneshot service - end

Jun 19 21:07:54 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.

Remarquez ici que parce que le service est toujours actif (même s'il a terminé son ExecStart), l'ExecStop n'a pas encore été exécuté. Maintenant, si vous exécutez systemctl stop oneshot-execstop-remainafterexit.service, voyons à quoi cela ressemble :

● oneshot-execstop-remainafterexit.service - Oneshot service test with ExecStop and RemainAfterExit

   Loaded: loaded (/etc/systemd/system/oneshot-execstop-remainafterexit.service; static; vendor preset: enabled)

   Active: inactive (dead)

 

Jun 19 21:06:54 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...

Jun 19 21:06:54 thstring20200619162314 bash[1708]: Oneshot service - start

Jun 19 21:07:54 thstring20200619162314 bash[1708]: Oneshot service - end

Jun 19 21:07:54 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.

Jun 19 21:08:58 thstring20200619162314 systemd[1]: Stopping Oneshot service test with ExecStop and RemainAfterExit...

Jun 19 21:08:58 thstring20200619162314 bash[1900]: Oneshot service - stop

Jun 19 21:08:58 thstring20200619162314 systemd[1]: oneshot-execstop-remainafterexit.service: Succeeded.

Jun 19 21:08:58 thstring20200619162314 systemd[1]: Stopped Oneshot service test with ExecStop and RemainAfterExit.

Maintenant nous pouvons voir que l'ExecStop a été exécuté car le service est maintenant inactif. Tout cela est intéressant, mais il n'est pas courant d'utiliser systemctl pour arrêter un service. Alors quand cela peut-il être utile ? Voir ci-dessous...

Exécuter un service à l'arrêt

En créant un service one-shot avec un ExecStop avec RemainAfterExit, c'est un excellent moyen d'exécuter efficacement quelque chose à l'arrêt. Voyons à quoi cela ressemble en pratique :

oneshot-execstop-remainafterexit-install.service

[Unit]

Description=Oneshot service test with ExecStop and RemainAfterExit

 

[Service]

Type=oneshot

RemainAfterExit=yes

ExecStart=/bin/bash -c "echo Oneshot service - start && sleep 60 && echo Oneshot service - end"

ExecStop=/bin/bash -c "echo Oneshot service - stop"

 

[Install]

WantedBy=multi-user.target

L'exécution de systemctl enable pour cette unité l'installera. Si vous deviez démarrer le service (ou redémarrer), vous verriez ceci :

● oneshot-execstop-remainafterexit-install.service - Oneshot service test with ExecStop and RemainAfterExit

   Loaded: loaded (/etc/systemd/system/oneshot-execstop-remainafterexit-install.service; enabled; vendor preset: enabled)

   Active: active (exited) since Fri 2020-06-19 21:14:02 UTC; 5s ago

 Main PID: 366 (code=exited, status=0/SUCCESS)

    Tasks: 0 (limit: 4087)

   Memory: 0B

   CGroup: /system.slice/oneshot-execstop-remainafterexit-install.service

 

Jun 19 21:13:02 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...

Jun 19 21:13:02 thstring20200619162314 bash[366]: Oneshot service - start

Jun 19 21:14:02 thstring20200619162314 bash[366]: Oneshot service - end

Jun 19 21:14:02 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.

JComme ci-dessus, notre ExecStop n'a pas encore été exécuté. Maintenant, faites un redémarrage, et regardez les logs :

-- Logs begin at Fri 2020-06-19 21:14:50 UTC, end at Fri 2020-06-19 21:18:47 UTC. --

Jun 19 21:14:51 thstring20200619162314 systemd[1]: Starting Oneshot service test with ExecStop and RemainAfterExit...

Jun 19 21:14:51 thstring20200619162314 bash[337]: Oneshot service - start

Jun 19 21:15:51 thstring20200619162314 bash[337]: Oneshot service - end

Jun 19 21:15:51 thstring20200619162314 systemd[1]: Started Oneshot service test with ExecStop and RemainAfterExit.

Jun 19 21:17:48 thstring20200619162314 systemd[1]: Stopping Oneshot service test with ExecStop and RemainAfterExit...

Jun 19 21:17:48 thstring20200619162314 bash[681]: Oneshot service - stop

Jun 19 21:17:49 thstring20200619162314 systemd[1]: oneshot-execstop-remainafterexit-install.service: Succeeded.

Jun 19 21:17:49 thstring20200619162314 systemd[1]: Stopped Oneshot service test with ExecStop and RemainAfterExit.

En fait, la machine a été arrêtée aux alentours de 21:17:48, ce qui a provoqué l'arrêt du service qui, à son tour, exécute ExecStop. C'est un moyen vraiment simple et efficace d'exécuter quelque chose à l'arrêt (comme un processus de nettoyage en douceur) ! Et ce qui est encore mieux, c'est que vous n'avez pas besoin d'avoir un ExecStart avec un service oneshot. Plus d'informations à ce sujet ci-dessous.

Plusieurs ExecStart

Un service simple ne peut avoir qu'une seule directive ExecStart. Mais un service simple peut avoir zéro, un ou plusieurs ExecStart. Si vous n'avez pas d'ExecStart, vous devez définir ExecStop (ainsi que RemainAfterExit). Il s'agirait d'un service qui ne s'exécuterait qu'à l'arrêt, et à aucun autre moment. Il ressemblerait à oneshot-execstop-remainafterexit-install.service mais sans l'ExecStart.

Comme mentionné ci-dessus, un service oneshot peut aussi avoir plusieurs ExecStart. Cela pourrait ressembler à :

oneshot-multiple-execstart.service

[Unit]

Description=Oneshot service test with multiple ExecStart

 

[Service]

Type=oneshot

ExecStart=/bin/bash -c "echo First"

ExecStart=/bin/bash -c "echo Second"

ExecStart=/bin/bash -c "echo Third"

Comme prévu, le résultat de la journalisation serait :

-- Logs begin at Mon 2020-06-22 13:24:01 UTC, end at Mon 2020-06-22 13:33:16 UTC. --

Jun 22 13:33:02 thstring20200622092223 systemd[1]: Starting Oneshot service test with multiple ExecStart...

Jun 22 13:33:02 thstring20200622092223 bash[1316]: First

Jun 22 13:33:02 thstring20200622092223 bash[1317]: Second

Jun 22 13:33:02 thstring20200622092223 bash[1318]: Third

Jun 22 13:33:02 thstring20200622092223 systemd[1]: oneshot-multiple-execstart.service: Succeeded.

Jun 22 13:33:02 thstring20200622092223 systemd[1]: Started Oneshot service test with multiple ExecStart.

En enchaînant les actions ExecStart, il vous permet de créer des flux de travail puissants directement dans une unité systemd. Mais que se passe-t-il si l'un de vos ExecStart a une défaillance ?

oneshot-multiple-execstart-failure.service

[Unit]

Description=Oneshot service test with multiple ExecStart and failure

 

[Service]

Type=oneshot

ExecStart=/bin/bash -c "echo First"

ExecStart=/bin/bash -c "false && echo Second"

ExecStart=/bin/bash -c "echo Third"

En essayant d'exécuter ce service, vous obtiendrez le résultat suivant :

$ sudo systemctl start oneshot-multiple-execstart-failure.service

Job for oneshot-multiple-execstart-failure.service failed because the control process exited with error code.

See "systemctl status oneshot-multiple-execstart-failure.service" and "journalctl -xe" for details.

 

$ sudo journalctl -u oneshot-multiple-execstart-failure.service

-- Logs begin at Mon 2020-06-22 13:24:01 UTC, end at Mon 2020-06-22 13:37:16 UTC. --

Jun 22 13:36:53 thstring20200622092223 systemd[1]: Starting Oneshot service test with multiple ExecStart and failure...

Jun 22 13:36:53 thstring20200622092223 bash[1441]: First

Jun 22 13:36:53 thstring20200622092223 systemd[1]: oneshot-multiple-execstart-failure.service: Main process exited, code=exited, status=1/FAILURE

Jun 22 13:36:53 thstring20200622092223 systemd[1]: oneshot-multiple-execstart-failure.service: Failed with result 'exit-code'.

Jun 22 13:36:53 thstring20200622092223 systemd[1]: Failed to start Oneshot service test with multiple ExecStart and failure.

Le service échoue et arrête l'exécution. Mais que faire si vous ne voulez pas que cet échec empêche le service de continuer ? Vous pouvez simplement ajouter un caractère - devant l'exécutable.

oneshot-multiple-execstart-failure-success.service

[Unit]

Description=Oneshot service test with multiple ExecStart and failure

 

[Service]

Type=oneshot

ExecStart=/bin/bash -c "echo First"

ExecStart=-/bin/bash -c "false && echo Second"

ExecStart=/bin/bash -c "echo Third"

Ce n'est pas évident, mais remarquez dans le second ExecStart que /bin/bash est précédé d'un -. Regardez maintenant cette sortie :

-- Logs begin at Mon 2020-06-22 13:24:01 UTC, end at Mon 2020-06-22 13:39:04 UTC. --

Jun 22 13:38:59 thstring20200622092223 systemd[1]: Starting Oneshot service test with multiple ExecStart and failure...

Jun 22 13:38:59 thstring20200622092223 bash[1553]: First

Jun 22 13:38:59 thstring20200622092223 bash[1555]: Third

Jun 22 13:38:59 thstring20200622092223 systemd[1]: oneshot-multiple-execstart-failure-success.service: Succeeded.

Jun 22 13:38:59 thstring20200622092223 systemd[1]: Started Oneshot service test with multiple ExecStart and failure.

Le deuxième ExecStart échoue comme prévu, mais il ne fait pas échouer l'exécution du service ou de l'arrêt et le troisième s'exécute.

Résumé

Lorsque vous devez décider du type de service à choisir entre le simple et le oneshot, voici quelques conseils :

    - Votre service doit-il être terminé avant que les services suivants ne soient exécutés ? Utilisez oneshot.

    - Vos services de suivi doivent-ils être exécutés pendant que ce service est en cours ? Utilisez simple.

    - S'agit-il d'un service de longue durée ? Utilisez probablement simple.

    - Avez-vous besoin de lancer ce service uniquement à l'arrêt ? Utilisez oneshot.

    - Avez-vous besoin d'avoir plusieurs commandes distinctes à exécuter ? Utilisez oneshot.